﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace TibiaMC
{
    internal class SystemHandle
    {
        #region Variables
        private NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO information;
        private string handleName;
        #endregion

        #region Private Properties
        private IntPtr Handle { get { return new IntPtr(this.information.Handle); } }
        #endregion

        #region Properties
        public string Name { get { return this.handleName; } }
        #endregion

        #region Private Methods
        private string ObjectName(IntPtr targetHandle)
        {
            int size = 0;
            int returnLength = 0;
            IntPtr objNameInfoPointer = Marshal.AllocHGlobal(size);

            try
            {
                uint NTSTATUS = NativeMethods.NtQueryObject(
                    targetHandle, 
                    NativeMethods.ObjectInformationClass.ObjectNameInformation,
                    objNameInfoPointer,
                    size,
                    out returnLength
                );

                while (NTSTATUS == NativeMethods.STATUS_INFO_LENGTH_MISMATCH)
                {
                    size = returnLength;
                    Marshal.FreeHGlobal(objNameInfoPointer);
                    objNameInfoPointer = Marshal.AllocHGlobal(size);

                    NTSTATUS = NativeMethods.NtQueryObject(
                        targetHandle,
                        NativeMethods.ObjectInformationClass.ObjectNameInformation,
                        objNameInfoPointer,
                        size,
                        out returnLength
                    );
                }

                if (NTSTATUS != NativeMethods.STATUS_SUCCESS)
                    return string.Empty;

                return ObjectNameInfo(objNameInfoPointer);
            }
            finally
            {
                Marshal.FreeHGlobal(objNameInfoPointer);
            }
        }
        private string ObjectNameInfo(IntPtr pointer)
        {
            NativeMethods.OBJECT_NAME_INFORMATION objNameInfo = (NativeMethods.OBJECT_NAME_INFORMATION)(
                Marshal.PtrToStructure(pointer, typeof(NativeMethods.OBJECT_NAME_INFORMATION))
            );

            NativeMethods.UNICODE_STRING name = objNameInfo.Name;
            ushort len = name.Length;
            if (len == 0)
                return string.Empty;

            return Marshal.PtrToStringUni(name.Buffer);
        }
        private string HandleName(IntPtr processHandle)
        {
            IntPtr targetHandle = NativeMethods.INVALID_HANDLE_VALUE;

            if (!NativeMethods.DuplicateHandle(
                processHandle,
                this.Handle,
                NativeMethods.GetCurrentProcess(),
                out targetHandle,
                0,
                false,
                NativeMethods.DuplicateOptions.DUPLICATE_SAME_ACCESS))
            {
                return string.Empty; // We can't throw here, because some handles doesn't support it.
            }

            string name = ObjectName(targetHandle);
            TryCloseHandle(targetHandle);
            return name;
        }
        #endregion

        #region Methods
        public void Close(IntPtr processHandle)
        {
            IntPtr handle = NativeMethods.INVALID_HANDLE_VALUE;

            if (!NativeMethods.DuplicateHandle(
                processHandle,
                this.Handle,
                NativeMethods.GetCurrentProcess(),
                out handle,
                0,
                false,
                NativeMethods.DuplicateOptions.DUPLICATE_CLOSE_SOURCE))
            {
                int lastError = Marshal.GetLastWin32Error();
                throw new Win32Exception(lastError);
            }

            TryCloseHandle(handle);
        }
        #endregion

        #region Private Static
        private static void TryCloseHandle(IntPtr handle)
        {
            if (!NativeMethods.CloseHandle(handle))
            {
                int lastError = Marshal.GetLastWin32Error();
                throw new Win32Exception(lastError);
            }
        }
        private static bool Is64Bits()
        {
            return IntPtr.Size == 8;// == 4 for 32 bits
        }
        #endregion

        #region Static
        public static IEnumerable<SystemHandle> EnumerateProcessHandles(int processId)
        {
            IntPtr processHandle = NativeMethods.OpenProcess(NativeMethods.ProcessAccessFlags.DupHandle, false, processId);
            if (processHandle == NativeMethods.INVALID_HANDLE_VALUE)
            {
                int lastError = Marshal.GetLastWin32Error();
                throw new Win32Exception(lastError);
            }

            int size = 0;
            int returnLength = 0;
            IntPtr systemHandle = NativeMethods.NULL;

            try
            {
                systemHandle = Marshal.AllocHGlobal(size);

                uint NTSTATUS = NativeMethods.NtQuerySystemInformation(
                    NativeMethods.CNST_SYSTEM_HANDLE_INFORMATION,
                    systemHandle,
                    size,
                    out returnLength
                );

                while (NTSTATUS == NativeMethods.STATUS_INFO_LENGTH_MISMATCH)
                {
                    size = returnLength;
                    Marshal.FreeHGlobal(systemHandle);
                    systemHandle = Marshal.AllocHGlobal(returnLength);

                    NTSTATUS = NativeMethods.NtQuerySystemInformation(
                        NativeMethods.CNST_SYSTEM_HANDLE_INFORMATION,
                        systemHandle,
                        size,
                        out returnLength
                    );
                }

                if (NTSTATUS != NativeMethods.STATUS_SUCCESS)
                    yield break;

                Type systemHandleInfoType = typeof(NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO);
                int systemHandleInfoSize = Marshal.SizeOf(systemHandleInfoType);

                //  In the following:
                //      IntPtr.Size == 8 for 64bits applications
                //      IntPtr.Size == 4 for 32bits applications

                //  idea behind systemHandle:
                //
                //  *(systemHandle) = NumberOfHandles;
                //  *(systemHandle + IntPtr.Size) = Handles;
                //  *(Handles + i * systemHandleInfoSize) = Handles[i];

                //  [StructLayout(LayoutKind.Sequential)]
                //  public struct SYSTEM_HANDLE
                //  {
                //      public IntPtr NumberOfHandles;
                //      public IntPtr Handles;
                //  }

                long count = (Is64Bits()) ? Marshal.ReadInt64(systemHandle) : ((long)Marshal.ReadInt32(systemHandle));
                IntPtr currentHandle = IntPtr.Add(systemHandle, IntPtr.Size);

                NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO systemHandleInfo;
                for (long i = 0; i < count; i++, currentHandle = IntPtr.Add(currentHandle, systemHandleInfoSize))
                {
                    systemHandleInfo = (NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO)(
                        Marshal.PtrToStructure(currentHandle, systemHandleInfoType)
                    );

                    if (systemHandleInfo.ProcessId == processId)
                        yield return new SystemHandle(processHandle, systemHandleInfo);
                }
            }
            finally
            {
                Marshal.FreeHGlobal(systemHandle);
                NativeMethods.CloseHandle(processHandle);
            }
        }
        #endregion

        #region Overrides
        public override string ToString()
        {
            if (this.handleName == string.Empty)
                return string.Concat("{Handle: ", this.Handle.ToString(), "}");

            return string.Concat("{Handle: ", this.Handle.ToString(), ", Name: ", this.handleName, "}");
        }
        public override bool Equals(object obj)
        {
            if (obj == null || GetType() != obj.GetType())
            {
                return false;
            }

            SystemHandle other = (SystemHandle)obj;
            return this.Handle == other.Handle;
        }
        public override int GetHashCode()
        {
            return this.Handle.GetHashCode();
        }
        #endregion

        #region Constructors
        private SystemHandle(IntPtr processHandle, NativeMethods.SYSTEM_HANDLE_TABLE_ENTRY_INFO systemHandleInformation)
        {
            this.information = systemHandleInformation;
            this.handleName = HandleName(processHandle);
        }
        #endregion
    }
}
